using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Lichtspiel.Math;
using OpenTK.Graphics.OpenGL;
using PixelFormat=System.Drawing.Imaging.PixelFormat;
using Rectangle=Lichtspiel.Math.Rectangle;
using Size=Lichtspiel.Math.Size;

namespace Lichtspiel.Display
{
    public class Image : IDisposable
    {
        private bool disposed = false;

        private static Color defaultColor = Color.White;
        private static Vector2 defaultScale = Vector2.One;
        private static Vector2 defaultScaleOrigin = Vector2.Zero;
        private Vector2 defaultRotationOrigin;
        private Rectangle defaultSourceRectangle;

        private Bitmap bitmap; // Used to load image
        private bool blendModeSet = false;
        private bool rebuild = true;
        private int texture; // Holds image data
        private VertexBufferObject vbo = new VertexBufferObject();

        public Image(string path) {
            vbo.vertices = new Vertex[4]; // Create 4 vertices for quad
            vbo.texcoords = new TexCoord[4]; // Texture coordinates for quad

            Load(path);
        }

        public int Width { get; private set; }
        public int Height { get; private set; }

        public Size Size {
            get { return new Size(Width, Height);}
        }

        public Vector2 Center { get; private set; }

        public Rectangle Rectangle { get; private set; }

        /// <summary>
        /// Loads image from harddisk into memory.
        /// </summary>
        /// <param name="path">Image path.</param>
        private void Load(string path) {
            // Load image
            try {
                bitmap = new Bitmap(path);
            } catch (ArgumentException e) {
                throw new FileNotFoundException(path);
            }

            // Generate texture
            GL.GenTextures(1, out texture);
            GL.BindTexture(TextureTarget.Texture2D, texture);

            // Store texture size
            Width = bitmap.Width;
            Height = bitmap.Height;
            Center = new Vector2(Width / 2f, Height / 2f);
            Rectangle = new Rectangle(0, 0, Width, Height);
            defaultRotationOrigin = Center;
            defaultSourceRectangle = Rectangle;

            BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
                                              ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0,
                          OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);

            bitmap.UnlockBits(data);

            // Setup filtering
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
                            (int) TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
                            (int) TextureMagFilter.Linear);
        }

        #region draw methods
        public void Draw(ref Rectangle destinationRectangle) {
            Draw(ref destinationRectangle, ref defaultSourceRectangle);
        }

        public void Draw(ref Rectangle destinationRectangle, ref Rectangle sourceRectangle) {
            Draw(ref destinationRectangle, ref sourceRectangle, ref defaultColor);
        }

        public void Draw(ref Rectangle destinationRectangle, ref Rectangle sourceRectangle, ref Color color) {
            Draw(ref destinationRectangle, ref sourceRectangle, ref color, BlendMode.Alpha);
        }

        public void Draw(ref Rectangle destinationRectangle, ref Rectangle sourceRectangle, ref Color color, BlendMode blendMode) {
            Draw(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, destinationRectangle.Height,
                 sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Width, sourceRectangle.Height, ref color, 0, 0, 0, 1,
                 1, 0, 0, blendMode);
        }

        public void Draw(ref Vector2 pos) {
            Draw(ref pos, ref defaultColor);
        }

        public void Draw(ref Vector2 pos, ref Color color) {
            Draw(ref pos, ref color, 0f);
        }

        public void Draw(ref Vector2 pos, ref Color color, BlendMode blendMode) {
            Draw(ref pos, ref color, 0f, ref defaultRotationOrigin, blendMode);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees) {
            Draw(ref pos, ref color, rotationDegrees, ref defaultRotationOrigin);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref defaultScale);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, BlendMode blendMode) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref defaultScale, ref defaultScaleOrigin, blendMode);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, ref Vector2 scale) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref defaultScaleOrigin);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, ref Vector2 scale,
                         ref Vector2 scaleOrigin) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, BlendMode.Alpha);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, ref Vector2 scale,
                         ref Vector2 scaleOrigin, ref Rectangle sourceRect) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, ref sourceRect, BlendMode.Alpha);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, ref Vector2 scale,
                         ref Vector2 scaleOrigin, BlendMode blendMode) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, ref defaultSourceRectangle, blendMode);
        }

        public void Draw(ref Vector2 pos, ref Color color, float rotationDegrees, ref Vector2 rotationOrigin, ref Vector2 scale,
                         ref Vector2 scaleOrigin, ref Rectangle sourceRect, BlendMode blendMode) {
            Draw((int)pos.X, (int)pos.Y, sourceRect.Width, sourceRect.Height, sourceRect.X, sourceRect.Y, sourceRect.Width,
                 sourceRect.Height, ref color, rotationDegrees, (int)rotationOrigin.X, (int)rotationOrigin.Y, scale.X,
                 scale.Y, (int) scaleOrigin.X, (int) scaleOrigin.Y, blendMode);
        }
        #endregion

        #region draw methods without ref
        public void Draw(Rectangle destinationRectangle) {
            Draw(ref destinationRectangle, ref defaultSourceRectangle);
        }

        public void Draw(Rectangle destinationRectangle, Rectangle sourceRectangle) {
            Draw(ref destinationRectangle, ref sourceRectangle, ref defaultColor);
        }

        public void Draw(Rectangle destinationRectangle, Rectangle sourceRectangle, Color color) {
            Draw(ref destinationRectangle, ref sourceRectangle, ref color, BlendMode.Alpha);
        }

        public void Draw(Rectangle destinationRectangle, Rectangle sourceRectangle, Color color, BlendMode blendMode) {
            Draw(destinationRectangle.X, destinationRectangle.Y, destinationRectangle.Width, destinationRectangle.Height,
                 sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Width, sourceRectangle.Height, ref color, 0, 0, 0, 1,
                 1, 0, 0, blendMode);
        }

        public void Draw(Vector2 pos) {
            Draw(ref pos, ref defaultColor);
        }

        public void Draw(Vector2 pos, Color color) {
            Draw(ref pos, ref color, 0f);
        }

        public void Draw(Vector2 pos, Color color, BlendMode blendMode) {
            Draw(ref pos, ref color, 0f, ref defaultRotationOrigin, blendMode);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees) {
            Draw(ref pos, ref color, rotationDegrees, ref defaultRotationOrigin);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref defaultScale);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, BlendMode blendMode) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref defaultScale, ref defaultScaleOrigin, blendMode);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, Vector2 scale) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref defaultScaleOrigin);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, Vector2 scale,
                         Vector2 scaleOrigin) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, BlendMode.Alpha);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, Vector2 scale,
                         Vector2 scaleOrigin, Rectangle sourceRect) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, ref sourceRect, BlendMode.Alpha);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, Vector2 scale,
                         Vector2 scaleOrigin, BlendMode blendMode) {
            Draw(ref pos, ref color, rotationDegrees, ref rotationOrigin, ref scale, ref scaleOrigin, ref defaultSourceRectangle, blendMode);
        }

        public void Draw(Vector2 pos, Color color, float rotationDegrees, Vector2 rotationOrigin, Vector2 scale,
                         Vector2 scaleOrigin, Rectangle sourceRect, BlendMode blendMode) {
            Draw((int)pos.X, (int)pos.Y, sourceRect.Width, sourceRect.Height, sourceRect.X, sourceRect.Y, sourceRect.Width,
                 sourceRect.Height, ref color, rotationDegrees, (int)rotationOrigin.X, (int)rotationOrigin.Y, scale.X,
                 scale.Y, (int)scaleOrigin.X, (int)scaleOrigin.Y, blendMode);
        }
        #endregion

        private void Draw(int x, int y, int w, int h, int imgX, int imgY, int imgW, int imgH, ref Color c, float rotDeg,
                          int rotOriginX, int rotOriginY, float scaleX, float scaleY, int scaleOriginX, int scaleOriginY,
                          BlendMode blendMode) {
            // Texture coordinates
            float u1 = 0.0f, u2 = 0.0f, v1 = 0.0f, v2 = 0.0f;

            // Calculate coordinates, prevent dividing by zero
            if (imgX != 0) {
                u1 = 1.0f / ((float)Width / (float)imgX);
            }
            if (imgW != 0) {
                u2 = 1.0f / ((float)Width / (float)(imgX + imgW));
            }
            if (imgY != 0) {
                v1 = 1.0f / ((float)Height / (float)imgY);
            }
            if (imgH != 0) {
                v2 = 1.0f / ((float)Height / (float)(imgY + imgH));
            }

            if (rebuild) {
                // Check if texture coordinates have changed
                if (vbo.texcoords[0].U != u1 || vbo.texcoords[1].U != u2 || vbo.texcoords[2].V != v1
                    || vbo.texcoords[0].V != v2) {
                    // Update texcoords for all vertices
                    BuildTexcoords(u1, u2, v1, v2);
                }

                // Check if position coordinates have changed
                if (vbo.vertices[0].X != x || vbo.vertices[2].Y != y || vbo.vertices[0].Y != y + h
                    || vbo.vertices[1].X != x + w) {
                    BuildVertices(x, y, w, h);
                }
            }

            // Prepare drawing
            Begin(x, y, rotDeg, rotOriginX, rotOriginY, ref c, scaleX, scaleY, scaleOriginX, scaleOriginY, blendMode);
            {
                // Bind texture
                GL.BindTexture(TextureTarget.Texture2D, texture);

                // Draw VBO
                vbo.Draw(vbo.vertices.Length, BeginMode.Quads);
            }
            End(x, y, rotDeg, rotOriginX, rotOriginY, ref c, scaleX, scaleY, scaleOriginX, scaleOriginY, blendMode);
        }

        private void Begin(int x, int y, float rotationDegrees, int rotOriginX, int rotOriginY, ref Color color,
                           float scaleX, float scaleY, int scaleOriginX, int scaleOriginY, BlendMode blendMode) {
            // blending

            if (blendMode != BlendMode.None) {
                blendModeSet = true;

                GLHelper.EnableBlend(blendMode, color);
            } else {
                blendModeSet = false;
            }

            GLHelper.Rotate(x + rotOriginX, y + rotOriginY, rotationDegrees);
            GLHelper.Scale(x + scaleOriginX, y + scaleOriginY, scaleX, scaleY);
        }

        /// <summary>
        /// Ends drawing.
        /// </summary>
        protected void End(int x, int y, float rotationDegrees, int rotOriginX, int rotOriginY, ref Color color,
                           float scaleX, float scaleY, int scaleOriginX, int scaleOriginY, BlendMode blendMode) {
            // transforms back
            GLHelper.Scale(x + scaleOriginX, y + scaleOriginY, 1/scaleX, 1/scaleY);
            GLHelper.Rotate(x + rotOriginX, y + rotOriginY, -rotationDegrees);

            // Disable blending
            if (blendModeSet) {
                GLHelper.DisableBlend();

                blendModeSet = false;
            }
        }

        /// <summary>
        /// Builds texcoords for quad.
        /// </summary>
        private void BuildTexcoords() {
            BuildTexcoords(0.0f, 1.0f, 0.0f, 1.0f);
        }


        /// <summary>
        /// Builds texcoords for quad.
        /// </summary>
        /// <param name="u1">U1.</param>
        /// <param name="u2">U2.</param>
        /// <param name="v1">V1.</param>
        /// <param name="v2">V2.</param>
        private void BuildTexcoords(float u1, float u2, float v1, float v2) {
            vbo.texcoords[0].U = u1;
            vbo.texcoords[0].V = v2;
            vbo.texcoords[1].U = u2;
            vbo.texcoords[1].V = v2;
            vbo.texcoords[2].U = u2;
            vbo.texcoords[2].V = v1;
            vbo.texcoords[3].U = u1;
            vbo.texcoords[3].V = v1;

            vbo.BuildTex();
        }


        /// <summary>
        /// Builds vertices for quad.
        /// </summary>
        /// <param name="x">X pos.</param>
        /// <param name="y">Y pos.</param>
        /// <param name="w">Width.</param>
        /// <param name="h">Height.</param>
        private void BuildVertices(int x, int y, int w, int h) {
            vbo.vertices[0].X = x;
            vbo.vertices[0].Y = y + h;
            vbo.vertices[1].X = x + w;
            vbo.vertices[1].Y = y + h;
            vbo.vertices[2].X = x + w;
            vbo.vertices[2].Y = y;
            vbo.vertices[3].X = x;
            vbo.vertices[3].Y = y;

            vbo.Build();
        }

        #region IDisposable Members

        public void Dispose() {
            Dispose(true);
        }

        private void Dispose(bool disposing) {
//             If you need thread safety, use a lock around these 
//             operations, as well as in your methods that use the resource.
            if (!disposed) {
                if (disposing) {
                    GL.DeleteTextures(1, ref texture);
                }

                // Indicate that the instance has been disposed.
                disposed = true;
            }

            
        }

        #endregion
    }
}